#include "enpc.h"
#include "ui_enpc.h"


int eNPC::nbTab = 0;

eNPC::eNPC(QWidget *parent) : QMainWindow(parent), ui(new Ui::eNPC)
{
    QStringList args = QCoreApplication::arguments(); // Arguments passés à l'ouverture

    docDock = NULL; // Pointeur sur le widget destiné à accueillir la documentation (script_commands)
    searchDock = NULL; // Pointeur sur le widget destiné à accueillir les résultats d'un "search all"
    myFont = QFont("Courier", 10); // Police du script par défaut

    ui->setupUi(this);

    /* Gestion du bouton Coller */
    connect(qApp->clipboard(), SIGNAL(dataChanged()), this, SLOT(enablePasteAction()));
    if(!qApp->clipboard()->text().isEmpty()) ui->actionColler->setEnabled(true);
    connect(ui->tabWidget, SIGNAL(currentChanged(int)), this, SLOT(on_tabWidget_currentChanged(int)));

    /* Divers */
    setAcceptDrops(true); // (Drag & drop) false @ textedit::textedit()
    setContextMenuPolicy(Qt::NoContextMenu);  // Empêche le clic droit sur la toolbar

    /* Chargement de l'historique des fichiers récents */
    for (int i = 0; i < MaxRecentFiles; ++i)
    {
        recentFileActs[i] = new QAction(this);
        recentFileActs[i]->setVisible(false);
        connect(recentFileActs[i], SIGNAL(triggered()), this, SLOT(openRecentFile()));
        ui->menuFichiers_r_cents->addAction(recentFileActs[i]);
    }

    updateRecentFileActions();

    openLastFilesList(); // Ouverture des derniers fichiers utilisés avant fermeture (ou d'un nouvel onglet, si aucun)
    if(args.count() > 1) openFilesWithArgs(args); // Ouverture via passage d'arguments (= drap & drop sur l'executable)
}

eNPC::~eNPC()
{
    delete ui;
}

void eNPC::closeEvent(QCloseEvent *e)
{
    QMapIterator<QWidget*, TextEdit*> i(tabList);
    QStringList lastFiles;

    while (i.hasNext())
    {
        i.next();

        if(mayBeSaved(i.key())) // Si l'onglet a été modifié
        {
            ui->tabWidget->setCurrentWidget(i.key());

            QMessageBox::StandardButton rep = askSave();
            if(rep == QMessageBox::Yes)
            {
                if(saveFile(i.key()))
                {
                    lastFiles.append(fileList.value(i.key())); // Le fichier a été sauvegardé, il sera considéré comme l'un des derniers fichiers ouverts
                    removeTab(i.key());
                }

                else
                    e->ignore();
            }

            else if(rep == QMessageBox::No)
                removeTab(i.key());

            else if(rep == QMessageBox::Cancel)
            {
                e->ignore();
                break;
            }
        }

        else // le fichier est déjà sauvegardé, on l'ajoute aux derniers fichiers ouverts
        {
            if(fileList.value(i.key()) != "") // Si ça n'est pas un nouvel onglet vierge
                lastFiles.append(fileList.value(i.key()));
        }
    }

    saveLastFilesList(lastFiles);
}



/* Le document a-t-il été modifié ? */
bool eNPC::mayBeSaved(QWidget *t)
{
    return tabList.value(t)->document()->isModified();
}


/* Ouverture & initialisation d'un nouvel onglet */
void eNPC::on_actionNouveau_triggered()
{
    QWidget *newTab = new QWidget(ui->tabWidget);
    QHBoxLayout *newLayout = new QHBoxLayout(newTab);
    TextEdit *newEdit = new TextEdit(myFont, newTab);
    QCompleter *completer = new QCompleter(modelFromFile(":/wordlist.txt", newEdit), newEdit);
    Highlighter *hl = new Highlighter(newEdit->document());

    newLayout->addWidget(newEdit);
    newLayout->setMargin(0);
    newTab->setLayout(newLayout);

    // Configuration de l'auto completion
    completer->setModelSorting(QCompleter::CaseInsensitivelySortedModel);
    completer->setCaseSensitivity(Qt::CaseInsensitive);
    completer->setWrapAround(false);
    newEdit->setCompleter(completer);

    // Nommage de l'onglet et ajout des composants (l'highlighter et le TextEdit) dans les QMap
    ui->tabWidget->addTab(newTab, "Script "+QString::number(++nbTab)); // Script 1, Script 2, ...

    highlighterList.insert(newTab, hl);
    tabList.insert(newTab, newEdit);

    // Ouverture d'un nouveau widget : focus sur la nouvelle zone de texte
    ui->tabWidget->setCurrentWidget(newTab);
    newEdit->setFocus();
    enableTabNeededActions(); // Avec les options de l'interface nécessaire

    // Gestion de l'interface...
    connect(newEdit->document(), SIGNAL(undoAvailable(bool)), ui->actionAnnuler, SLOT(setEnabled(bool)));
    connect(newEdit->document(), SIGNAL(redoAvailable(bool)), ui->actionR_peter, SLOT(setEnabled(bool)));
    connect(newEdit, SIGNAL(copyAvailable(bool)), ui->actionCouper, SLOT(setEnabled(bool)));
    connect(newEdit, SIGNAL(copyAvailable(bool)), ui->actionCopier, SLOT(setEnabled(bool)));
    connect(newEdit->document(), SIGNAL(modificationChanged(bool)), this, SLOT(setTabWilcard()));
    connect(newEdit, SIGNAL(cursorPositionChanged()), this, SLOT(matchParentheses()));

    // Indique à l'UI que la doc est prête à être affichée
    connect(newEdit, SIGNAL(documentationReady(QString, QString)), this, SLOT(showDocumentation(QString, QString)));

    newEdit->showLineNumberArea(ui->actionNum_ros_de_ligne->isChecked());
}



void eNPC::removeTab(QWidget* t)
{
    tabList.remove(t);
    fileList.remove(t);
    delete highlighterList.value(t);
    highlighterList.remove(t);

    QList<QWidget *> allWidgets = t->findChildren<QWidget *>();
    QWidget* temp;
    foreach(temp, allWidgets)
        temp->deleteLater();

    delete t;

    if(ui->tabWidget->count() == 0) disableTabNeededActions();
}

void eNPC::on_tabWidget_tabCloseRequested(int index)
{
    if(mayBeSaved(ui->tabWidget->widget(index))) // Le document a été modifié : Sauvegarde ?
    {
        QMessageBox::StandardButton ret = askSave();
        if(ret == QMessageBox::Yes)
        {
            /* L'utilisateur a validé sa sauvegarde : suppression du tab */
            if(saveFile(ui->tabWidget->widget(index))) removeTab(ui->tabWidget->widget(index));
        }

        else if(ret == QMessageBox::No) removeTab(ui->tabWidget->widget(index));
    }

    else removeTab(ui->tabWidget->widget(index));
}



/* Confirmation de sauvegarde */
QMessageBox::StandardButton eNPC::askSave()
{
    return QMessageBox::warning(this, tr("Sauvegarde"), tr("Ce script a été modifié.\nSouhaitez-vous sauvegarder la version courante ?"),
           QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
}



bool eNPC::saveFile(QWidget* t)
{
    if (fileList.value(t).isEmpty())
        return saveFileAs(t); /* L'onglet ne fait pas parti des fichiers connus => création d'un nouveau fichier */

    QFile file(fileList.value(t));

    bool success = file.open(QIODevice::WriteOnly | QIODevice::Text);

    if (success)
    {
        QTextDocumentWriter writer(fileList.value(t));
        writer.setFormat("plaintext");
        writer.setCodec(QTextCodec::codecForName("ISO-8859-1"));

        if (writer.write(tabList.value(t)->document()))
        {
            tabList.value(t)->document()->setModified(false);
            ui->tabWidget->setTabText(ui->tabWidget->indexOf(t), getFileNameFromPath(fileList.value(t)));
            setWindowTitle("eNPC - "+fileList.value(t));

            writeInRecentFileList(fileList.value(t));
        }

        else
        {
            QMessageBox::critical(this, tr("Sauvegarde impossible"), tr("Écriture dans le fichier refusée."), QMessageBox::Ok);
            success = false;
        }
    }

    else
        QMessageBox::critical(this, tr("Sauvegarde impossible"), tr("Écriture dans le fichier refusée."), QMessageBox::Ok);

    return success;
}

bool eNPC::saveFileAs(QWidget* t)
{
    QString fn = QFileDialog::getSaveFileName(this, tr("Enregistrer sous..."),
                                              QString(), tr("Fichier texte (*.txt);;eAthena Script (*.eas);;eAthena NPC (*.npc)"));
    if (fn.isEmpty())
        return false;
    fileList.insert(t, fn);
    return saveFile(t);
}

QAbstractItemModel *eNPC::modelFromFile(const QString& fileName, TextEdit *completer)
{
    QFile file(fileName);
    if (!file.open(QFile::ReadOnly))
        return new QStringListModel(completer);

#ifndef QT_NO_CURSOR
    QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
#endif

    QStringList words;

    while (!file.atEnd()) {
        QByteArray line = file.readLine();
        if (!line.isEmpty())
            words << line.trimmed();
    }

#ifndef QT_NO_CURSOR
    QApplication::restoreOverrideCursor();
#endif

    return new QStringListModel(words, completer);
}

void eNPC::on_actionEnregistrer_triggered()
{
    saveFile(ui->tabWidget->currentWidget());
}

void eNPC::on_actionEnregistrer_sous_triggered()
{
    saveFileAs(ui->tabWidget->currentWidget());
}

void eNPC::on_actionAnnuler_triggered()
{
    getCurrentTextEdit()->undo();
}

void eNPC::on_actionR_peter_triggered()
{
    getCurrentTextEdit()->redo();
}

void eNPC::on_actionQuitter_triggered()
{
    qApp->closeAllWindows();
}

void eNPC::on_actionFermer_ce_script_triggered()
{
    on_tabWidget_tabCloseRequested(ui->tabWidget->indexOf(ui->tabWidget->currentWidget()));
}

void eNPC::on_actionCopier_triggered()
{
    getCurrentTextEdit()->copy();
}

void eNPC::on_actionColler_triggered()
{
    getCurrentTextEdit()->paste();
}

void eNPC::on_actionCouper_triggered()
{
    getCurrentTextEdit()->cut();
}

void eNPC::on_actionRechercher_triggered()
{
    SearchAndReplace sDialog(this, getCurrentTextEdit());
    connect(&sDialog, SIGNAL(searchAllResults(QMap<int,QString>&, QString&)), this, SLOT(displaySearchAllResults(QMap<int,QString>&, QString&)));
    sDialog.exec();
}

void eNPC::on_actionS_lectionner_tout_triggered()
{
    getCurrentTextEdit()->selectAll();
}

QString eNPC::getFileNameFromPath(QString path)
{
    QRegExp fileName("[\\\\|/]?([^\\\\|/]+\\.(eas|txt|npc)$)");
    return (fileName.indexIn(path) >= 0) ? fileName.cap(1) : "";
}

void eNPC::on_actionOuvrir_triggered()
{
    QString fn = QFileDialog::getOpenFileName(this, tr("Ouvrir un fichier..."),
                                               ".", tr("Scripts (*.txt *.eas *.npc)"));
    if (!fn.isEmpty())
        openFile(fn, getFileNameFromPath(fn));
}


void eNPC::openFilesWithArgs(QStringList args)
{
    for(int i = 1; i < args.count(); i++)
    {
        QString newFile = getFileNameFromPath(args.at(i));
        if(!newFile.isEmpty())
            openFile(args.at(i), newFile);

        else QMessageBox::critical(this, tr("Argument invalide"), tr("Impossible d'ouvrir le fichier ")+args.at(i)+" !");
    }
}


bool eNPC::openFile(QString filePath, QString fileName, bool silence)
{
    QMapIterator<QWidget*, QString> i(fileList);

    filePath = filePath.replace('\\', '/');

    while (i.hasNext()) /* Vérifie que le fichier ne soit pas déjà ouvert */
    {
        i.next();
        if(i.value() == filePath)
        {
            ui->tabWidget->setCurrentWidget(i.key());
            return true;
        }
    }

    QFile file(filePath);

    if (!QFile::exists(filePath))
    {
        if(!silence) QMessageBox::critical(this, tr("Erreur fichier"), tr("Fichier introuvable."), QMessageBox::Ok);
        return false;
    }

    if(!file.open(QFile::ReadOnly))
    {
        QFile file("./"+fileName);
        if(!file.open(QFile::ReadOnly))
         {
            if(!silence) QMessageBox::critical(this, tr("Erreur fichier"), tr("Impossible d'ouvrir le fichier spécifié."), QMessageBox::Ok);
            return false;
        }
    }

    on_actionNouveau_triggered();
    QWidget *t = ui->tabWidget->currentWidget();

    fileList.insert(t, filePath);
    QByteArray data = file.readAll();
    QString str = QString::fromLatin1(data);

    tabList.value(t)->setPlainText(str);
    setWindowTitle("eNPC - "+fileList.value(t));
    ui->tabWidget->setTabText(ui->tabWidget->indexOf(t), fileName);
    writeInRecentFileList(filePath);

    return true;
}



void eNPC::on_actionBarre_de_raccourcis_triggered()
{
    (ui->mainToolBar->isVisible()) ? ui->mainToolBar->setVisible(false) : ui->mainToolBar->setVisible(true);
}



void eNPC::on_actionNum_ros_de_ligne_triggered()
{
    QMap<QWidget*, TextEdit*>::iterator i;

    for (i = tabList.begin(); i != tabList.end(); ++i)
        i.value()->showLineNumberArea(ui->actionNum_ros_de_ligne->isChecked());
}

void eNPC::on_actionPolice_triggered()
{
    bool ok;
    QFont font = QFontDialog::getFont(&ok, getCurrentTextEdit()->font(), this);

    if (ok)
    {
        QMap<QWidget*, TextEdit*>::iterator i;

        for (i = tabList.begin(); i != tabList.end(); ++i)
            i.value()->setFont(font);
    }
}

void eNPC::on_actionEn_t_te_triggered()
{
    QStringList header;
    header << "//===== eAthena Script ======================================="
    << "//= <Script name>"
    << "//===== By: =================================================="
    << "//= <>"
    << "//===== Current Version: ====================================="
    << "//= <>"
    << "//===== Compatible With: ====================================="
    << "//= <>"
    << "//===== Description: ========================================="
    << "//= <>"
    << "//===== Additional Comments: ================================="
    << "//= <>"
    << "//============================================================";

    QTextCursor cur = getCurrentTextEdit()->textCursor();
    cur.setPosition(0);
    getCurrentTextEdit()->setTextCursor(cur);
    getCurrentTextEdit()->centerCursor();

    foreach(const QString &line, header)
        getCurrentTextEdit()->textCursor().insertText(line+"\n");
}

void eNPC::showDocumentation(QString content, QString command)
{
    QString currentCMD;

    if(docDock != NULL) // Hide the doc
    {
        currentCMD = docDock->getCommand();
        docDock->deleteLater();
        docDock = NULL;
    }

    if(currentCMD != command) // Show the doc
    {
        docDock = new Documentation("Documentation : "+command, this);
        addDockWidget(Qt::RightDockWidgetArea, docDock);
        docDock->setContent(content);
        docDock->setCommand(command);
    }
}

void eNPC::on_actionTout_fermer_triggered()
{
    QMapIterator<QWidget*, TextEdit*> i(tabList);

    while (i.hasNext())
    {
        i.next();

        if(mayBeSaved(i.key())) // Si l'onglet a été modifié
        {
            ui->tabWidget->setCurrentWidget(i.key());

            QMessageBox::StandardButton ret = askSave();
            if(ret == QMessageBox::Yes)
            {
                saveFile(i.key());
                removeTab(i.key());
            }

            if(ret == QMessageBox::No)
                removeTab(i.key());
        }
        else removeTab(i.key());
    }
}

void eNPC::disableTabNeededActions()
{
    ui->actionRechercher->setEnabled(false);
    ui->actionAller->setEnabled(false);
    ui->actionAnnuler->setEnabled(false);
    ui->actionR_peter->setEnabled(false);
    ui->actionRemplacer->setEnabled(false);
    ui->actionColler->setEnabled(false);
    ui->actionCopier->setEnabled(false);
    ui->actionCouper->setEnabled(false);
    ui->actionEn_t_te->setEnabled(false);
    ui->actionS_lectionner_tout->setEnabled(false);
    ui->actionEnregistrer->setEnabled(false);
    ui->actionEnregistrer_sous->setEnabled(false);
    ui->actionFermer_ce_script->setEnabled(false);
    ui->actionTout_fermer->setEnabled(false);
    ui->actionPolice->setEnabled(false);
    ui->actionNum_ros_de_ligne->setEnabled(false);
    ui->actionMenu->setEnabled(false);
    ui->actionMessages->setEnabled(false);
    ui->actionCouleur->setEnabled(false);
    ui->actionDocumentation->setEnabled(false);
    ui->actionDupliquer_la_ligne->setEnabled(false);
}

void eNPC::enableTabNeededActions()
{
    ui->actionRechercher->setEnabled(true);
    ui->actionRemplacer->setEnabled(true);
    ui->actionAller->setEnabled(true);
    ui->actionEn_t_te->setEnabled(true);
    ui->actionS_lectionner_tout->setEnabled(true);
    ui->actionEnregistrer->setEnabled(true);
    ui->actionEnregistrer_sous->setEnabled(true);
    ui->actionFermer_ce_script->setEnabled(true);
    ui->actionTout_fermer->setEnabled(true);
    ui->actionPolice->setEnabled(true);
    ui->actionNum_ros_de_ligne->setEnabled(true);
    ui->actionMenu->setEnabled(true);
    ui->actionMessages->setEnabled(true);
    ui->actionCouleur->setEnabled(true);
    ui->actionDocumentation->setEnabled(true);
    ui->actionDupliquer_la_ligne->setEnabled(true);
}

void eNPC::on_action_propos_triggered()
{
    QMessageBox::about(this, tr("À propos d'eNPC"), tr("eNPC Open Beta. Informations sur <a href=\"http://frathena.net\">http://frathena.net</a>.<br/><br/>Merci à <strong>Jonathan</strong>, <strong>DarkIfrit</strong>, <strong>Zouki</strong>, <strong>Havoc</strong> et <strong>Latios</strong> pour leur soutien.<br/>À <strong>Myoo</strong> pour les chibi fan-arts.<br/>Pour finir, à <strong>HeinzKDE</strong> pour la base du marquage des parenthèses / accolades.<br/><br/>Développé par <strong>Vince</strong>."));
}

void eNPC::enablePasteAction()
{
    if(!qApp->clipboard()->text().isNull() && !qApp->clipboard()->text().isEmpty())
        ui->actionColler->setEnabled(true);
    else
        ui->actionColler->setEnabled(false);
}

void eNPC::on_tabWidget_currentChanged(int index)
{
    setWindowTitle("eNPC");

    QWidget *t = ui->tabWidget->widget(index);
    if(!fileList.value(t).isEmpty())
        setWindowTitle(windowTitle()+" - "+fileList.value(t));
}

void eNPC::on_actionAller_triggered()
{
    bool ok;
    int lastBlockNumber = getCurrentTextEdit()->document()->blockCount();
    int lineNumber = QInputDialog::getInt(this, tr("Aller à..."), tr("Ligne : "), 1, 1, lastBlockNumber, 10, &ok);

    if(ok)
    {
        goToLine(lineNumber);
    }
}

void eNPC::goToLine(int _lineNumber)
{
    const QTextBlock &block =  getCurrentTextEdit()->document()->findBlockByNumber(_lineNumber-1);
    QTextCursor cursor(block);
    cursor.movePosition(QTextCursor::StartOfBlock);
    getCurrentTextEdit()->setTextCursor(cursor);
    getCurrentTextEdit()->centerCursor();
    getCurrentTextEdit()->setFocus();
}


void eNPC::goToSearchResultLine(int lineNumber)
{
    goToLine(lineNumber);
}

void eNPC::setTabWilcard()
{
    QWidget* t = ui->tabWidget->currentWidget();

    if(getCurrentTextEdit()->document()->isModified())
    {
        if(!(ui->tabWidget->tabText(ui->tabWidget->indexOf(t)).contains("*")))
            ui->tabWidget->setTabText(ui->tabWidget->indexOf(t), ui->tabWidget->tabText(ui->tabWidget->indexOf(t))+"*");
    }

    else
        if(ui->tabWidget->tabText(ui->tabWidget->indexOf(t)).contains("*"))
            ui->tabWidget->setTabText(ui->tabWidget->indexOf(t), ui->tabWidget->tabText(ui->tabWidget->indexOf(t)).replace('*', ""));

}



void eNPC::dragEnterEvent(QDragEnterEvent* event)
{
    if (event->mimeData()->hasFormat("text/uri-list"))
        event->acceptProposedAction();
}



void eNPC::dropEvent(QDropEvent* event)
{
    QList<QUrl> urls = event->mimeData()->urls();
    if (urls.isEmpty())
        return;

    QString filePath = urls.first().toLocalFile();
    if (filePath.isEmpty())
        return;

    QStringList args;
    args << ""; /* args[0] non utilisé */
    for(int i = 0; i < urls.count(); i++)
        args << urls.value(i).toLocalFile(); /* file:// -> X:\ */

    openFilesWithArgs(args); /* Ouverture "multiple" */
}

void eNPC::on_actionRemplacer_triggered()
{
    SearchAndReplace sDialog(this, getCurrentTextEdit(), true);
    sDialog.exec();
}

TextEdit* eNPC::getCurrentTextEdit()
{
    return tabList.value(ui->tabWidget->currentWidget());
}

void eNPC::on_actionDupliquer_la_ligne_triggered()
{
    QTextCursor cur = getCurrentTextEdit()->textCursor();
    cur.movePosition(QTextCursor::EndOfLine); // Déplace le curseur à la fin de la ligne pour l'insertion de la nouvelle
    cur.insertText("\n"+cur.block().text());
    // En pratique, le curseur ne change pas de position, car on ne fait pas appel à SetCursor() du TextEdit
}

void eNPC::on_actionDocumentation_triggered()
{
    getCurrentTextEdit()->showDocumentation(getCurrentTextEdit()->textUnderCursor());
}

QString eNPC::getSelectedColor()
{
    QColorDialog colorDialog;
    QColor color = colorDialog.getColor(); /* affiche la colorbox et récupère l'éventuelle saisie */

    if(!color.isValid()) return "";
    return color.name().remove("#"); /* Supprime le # du #RRGGBB */

}

void eNPC::fillColor(QPlainTextEdit* te)
{
    QString insertedColor = getSelectedColor(); /* Si l'utilisateur n'a rien saisi, 000000 est envoyé par défaut */

    if(!insertedColor.isEmpty()) /* L'utilisateur a bien saisi une couleur */
    {
        if(te->textCursor().hasSelection())
        {
            QString selection = te->textCursor().selection().toPlainText();

            te->textCursor().clearSelection();
            te->textCursor().movePosition(QTextCursor::NextWord, QTextCursor::KeepAnchor);
            te->textCursor().insertText("^"+insertedColor+selection+"^000000");
        }

        else
        {
            te->textCursor().insertText("^"+insertedColor+tr("texte coloré")+"^000000");
            te->find(tr("texte coloré"), QTextDocument::FindBackward);
        }
    }

    te->setFocus();
}

void eNPC::on_actionCouleur_triggered()
{
    fillColor(getCurrentTextEdit());
}

void eNPC::on_actionMessages_triggered()
{
    MesWizard mesWizard(this);
    connect(&mesWizard, SIGNAL(mesInserted(QString)), this, SLOT(insertGivenMes(QString)));
    mesWizard.exec();
}

void eNPC::insertGivenMes(QString input)
{
    QStringList messages = input.split("\n");

    foreach(const QString &mes, messages)
        if(mes != "<NEXT>") getCurrentTextEdit()->textCursor().insertText("mes \""+mes+"\";\n");
        else getCurrentTextEdit()->textCursor().insertText("next;\n");
}

void eNPC::on_actionMenu_triggered()
{
    MenuWizard menuWizard(this);
    connect(&menuWizard, SIGNAL(menuInserted(QStringList, QStringList, bool)), this, SLOT(insertGivenMenu(QStringList, QStringList, bool)));
    menuWizard.exec();
}

void eNPC::insertGivenMenu(QStringList cases, QStringList labels, bool labeled)
{
    QString result("");

    if(labeled) /* Utilisation de "menu" */
    {
        result = "menu ";
        for(int i = 0; i < cases.size(); i++)
            result+= "\""+cases.at(i)+"\", "+labels.at(i)+", ";

        result = result.replace(result.size()-2, 2, ";\n\n");

        for(int i = 0; i < labels.size(); i++) /* mets en place les labels */
            result+= ""+labels.at(i)+":\nclose;\n\n";

    }

    else
    {
        result = "switch(select(";
        for(int i = 0; i < cases.size(); i++)
            result+= "\""+cases.at(i)+"\", ";

        result = result.replace(result.size()-2, 2, "))");
        result+= "\n{\n"; /* ouverture du switch */

        for(int i = 0; i < cases.size(); i++) /* construit la base du switch */
            result+= "\tcase "+QString::number(i+1)+":\n\t\t\n\tbreak;\n\n";

        result+="}";
    }

    getCurrentTextEdit()->textCursor().insertText(result);
}


void eNPC::matchParentheses()
{
    QTextBlock textBlock = getCurrentTextEdit()->textCursor().block();
    TextBlockData *data = static_cast<TextBlockData *>(textBlock.userData());

    if (data)
    {
        QVector<ParenthesisInfo *> infos = data->parentheses();
        int pos = getCurrentTextEdit()->textCursor().block().position();

        for (int i=0; i<infos.size(); ++i)
        {
            ParenthesisInfo *info = infos.at(i);
            int curPos = getCurrentTextEdit()->textCursor().position() - textBlock.position();

            // à gauche de la parenthèse ?
            if (info->position == curPos-1 && (info->character == '(' || info->character == '{') )
            {
                if (matchLeft(textBlock, i+1, 0, info->character))
                    createSelection(pos + info->position);
            }

            // à droite de la parenthèse ?
            if (info->position == curPos-1 && (info->character == ')' || info->character == '}') )
            {
                if (matchRight(textBlock, i-1, 0, info->character))
                    createSelection(pos + info->position);
            }
        }
    }
}


bool eNPC::matchLeft(QTextBlock currentBlock, int index, int numLeft, char toMatch)
{
    TextBlockData *data = static_cast<TextBlockData *>(currentBlock.userData());
    QVector<ParenthesisInfo *> infos = data->parentheses();
    int docPos = currentBlock.position();

    // correspondance sur la même ligne ?
    for (; index<infos.size(); ++index)
    {
        ParenthesisInfo *info = infos.at(index);

        if (info->character == toMatch)
        {
            ++numLeft;
            continue;
        }

        if ( (info->character == getOppositeCharOf(toMatch)) && numLeft == 0)
        {
            createSelection(docPos + info->position);
            return true;
        }

        else // On ne décrémente que si on travaille avec des caractères du même type
            if(info->character == getOppositeCharOf(toMatch))
                --numLeft;
    }

    // Pas de correspondance ? On essaie le bloc suivant
    currentBlock = currentBlock.next();
    if (currentBlock.isValid())
            return matchLeft(currentBlock, 0, numLeft, toMatch);

    // Pas de correspondance du tout
    return false;
}

bool eNPC::matchRight(QTextBlock currentBlock, int index, int numRight, char toMatch)
{
    TextBlockData *data = static_cast<TextBlockData *>(currentBlock.userData());
    QVector<ParenthesisInfo *> infos = data->parentheses();
    int docPos = currentBlock.position();

    // correspondance sur la même ligne ?
    for (int j=index; j>=0; --j)
    {
        ParenthesisInfo *info = infos.at(j);

        if (info->character == toMatch)
        {
            ++numRight;
            continue;
        }

        if ( (info->character == getOppositeCharOf(toMatch)) && numRight == 0)
        {
            createSelection(docPos + info->position);
            return true;
        }

        else // On ne décrémente que si on travaille avec des caractères du même type
            if(info->character == getOppositeCharOf(toMatch))
                --numRight;
    }

    // Pas de correspondance ? On essaie le bloc suivant
    currentBlock = currentBlock.previous();

    if (currentBlock.isValid())
    {
        // redétermine d'abord le bon index
        TextBlockData *data = static_cast<TextBlockData *>(currentBlock.userData());
        QVector<ParenthesisInfo *> infos = data->parentheses();

        return matchRight(currentBlock, infos.size()-1, numRight, toMatch);
    }

    // Pas de correspondance du tout
    return false;
}


/* Marquage de la paire de parenthèse ou accolades en cours */
void eNPC::createSelection(int pos)
{
    QList<QTextEdit::ExtraSelection> selections = getCurrentTextEdit()->extraSelections();
    QTextEdit::ExtraSelection selection;
    QTextCharFormat format = selection.format;
    format.setBackground(QColor(0, 20, 200, 100));
    selection.format = format;

    QTextCursor cursor = getCurrentTextEdit()->textCursor();
    cursor.setPosition(pos);
    cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);

    selection.cursor = cursor;
    selections.append(selection);
    getCurrentTextEdit()->setExtraSelections(selections);
}


/* Utilisé dans la recherche du caractère fermant ou ouvrant */
char eNPC::getOppositeCharOf(char toMatch)
{
    switch(toMatch)
    {
        case '(': return ')';
        case ')': return '(';
        case '{': return '}';
        case '}': return '{';
    }

    return '\0';
}

void eNPC::openRecentFile()
{
    QAction *action = qobject_cast<QAction *>(sender());
    if (action)
    {
        // Si on n'arrive pas à ouvrir le fichier, on le supprime des fichiers récents
        if(!openFile(action->data().toString(), QFileInfo(action->data().toString()).fileName()))
        {
            QSettings settings("eNPC", "eSettings");
            QStringList files = settings.value("recentFileList").toStringList();
            files.removeAll(action->data().toString());
            settings.setValue("recentFileList", files);
            updateRecentFileActions();
        }
    }
}

void eNPC::writeInRecentFileList(const QString &fileName)
{
    setWindowFilePath(fileName);

    QSettings settings("eNPC", "eSettings");
    QStringList files = settings.value("recentFileList").toStringList();
    files.removeAll(fileName);
    files.prepend(fileName);
    while (files.size() > MaxRecentFiles) files.removeLast();

    settings.setValue("recentFileList", files);

    updateRecentFileActions();
}

void eNPC::updateRecentFileActions()
{
    QSettings settings("eNPC", "eSettings");
    QStringList files = settings.value("recentFileList").toStringList();

    int numRecentFiles = qMin(files.size(), (int)MaxRecentFiles);

    for (int i = 0; i < numRecentFiles; ++i)
    {
        QString text = tr("&%1 %2").arg(i + 1).arg(QFileInfo((files[i])).fileName());
        recentFileActs[i]->setText(text);
        recentFileActs[i]->setData(files[i]);
        recentFileActs[i]->setVisible(true);
    }

    for (int j = numRecentFiles; j < MaxRecentFiles; ++j)
        recentFileActs[j]->setVisible(false);
}

void eNPC::saveLastFilesList(const QStringList &files)
{
    QSettings settings("eNPC", "eSettings");
    settings.setValue("lastFiles", files);
}

void eNPC::openLastFilesList()
{
    QSettings settings("eNPC", "eSettings");
    QStringList files = settings.value("lastFiles").toStringList();

    if(files.size() > 0)
        foreach(QString file, files)
            openFile(file, QFileInfo(file).fileName(), true); // mode silencieux ("true") pour éviter des erreurs dûes à des fichiers manquants

    else
        on_actionNouveau_triggered();
}

void eNPC::displaySearchAllResults(QMap<int, QString> &_results, QString &_input)
{
    if(searchDock == NULL)
    {
        searchDock = new SearchDock(tr("Nombre de résultats : ")+QString::number(_results.size()), this);
        connect(searchDock, SIGNAL(resultDoubleClicked(int)), this, SLOT(goToSearchResultLine(int)));
    }

    searchDock->refresh(_results, _input);
    addDockWidget(Qt::BottomDockWidgetArea, searchDock);
}
